package gameoflife;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Writer;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.regex.Matcher;

public class Pattern {

	private String name;
	private String description;

	private SortedSet<Position> positions;
	private Rule rule;

	public Pattern(Rule rule, Collection<Position> cells) {
		this.positions = new TreeSet<Position>(cells);
		this.rule = rule;
	}

	public Set<Position> getPositions() {
		return positions;
	}

	public Rule getRule() {
		return rule;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	private static Pattern loadLIF(InputStream input) {
		Set<Position> positions = new HashSet<Position>();
		Rule rule = Rule.LIFE;
		StringBuffer desc = new StringBuffer();

		BufferedReader reader = new BufferedReader(new InputStreamReader(input));
		try {
			int x = 0;
			int y = 0;
			while (reader.ready()) {
				String line = reader.readLine();

				if (line.startsWith("#R")) {
					rule = Rule.createFromString(line.substring(2));
				}
				if (line.startsWith("#D")) {
					desc.append(line.substring(2).trim() + "\n");
				}
				if (line.startsWith("#P")) {
					String[] coords = line.split("\\s", 3);
					if (coords.length == 3) {
						x = new Integer(coords[1]);
						y = new Integer(coords[2]);
					}
				}
				if (!line.startsWith("#")) {
					for (int dx = 0; dx < line.length(); dx++) {
						if (line.charAt(dx) == '*') {
							positions.add(new Position(x + dx, y));
						}
					}
					y++;
				}
			}
			input.close();
		} catch (IOException e) {
		}
		Pattern pattern = new Pattern(rule, positions);
		pattern.setDescription(desc.toString());
		return pattern;
	}

	private static Pattern loadRLE(InputStream input) {
		Set<Position> positions = new HashSet<Position>();
		Rule rule = Rule.LIFE;
		StringBuilder desc = new StringBuilder();

		BufferedReader reader = new BufferedReader(new InputStreamReader(input));
		try {
			// comments
			String line;
			while ((line = reader.readLine()) != null && (line.startsWith("#") || line.trim().isEmpty())) {
				if (line.trim().isEmpty()) {
					continue;
				}
				line = line.substring(1);
				if (line.toLowerCase().startsWith("c")) {
					line = line.substring(1);
				}
				desc.append(line.trim() + "\n");
			}

			// offset and rule
			if (line != null) {
				String regex = "^x\\s*=\\s*(-?\\d+)\\s*,\\s*y\\s*=\\s*(-?\\d+)\\s*(,\\s*rule\\s*=\\s*(.*))?$";
				Matcher m = java.util.regex.Pattern.compile(regex).matcher(line);
				if (m.find()) {
					int x = new Integer(m.group(1));
					int y = new Integer(m.group(2));
					if (m.groupCount() == 4) {
						rule = Rule.createFromString(m.group(4));
					}

					// sequence
					int ch;
					int offsetX = x;
					int runCount = 0;
					while ((ch = reader.read()) != -1 && ch != '!') {
						if (!Character.isWhitespace(ch)) {
							switch (Character.toLowerCase(ch)) {
							case '$':
								runCount = runCount == 0 ? 1 : runCount;
								y += runCount;
								runCount = 0;
								x = offsetX;
								break;
							case 'b':
								runCount = runCount == 0 ? 1 : runCount;
								x += runCount;
								runCount = 0;
								break;
							case 'o':
								runCount = runCount == 0 ? 1 : runCount;
								for (int i = 0; i < runCount; i++) {
									positions.add(new Position(x++, y));
								}
								runCount = 0;
								break;
							default:
								if (Character.isDigit(ch)) {
									runCount = Character.digit(ch, 10) + 10 * runCount;
								}
							}
						}
					}
				}
			}
			input.close();
		} catch (IOException e) {
		}
		Pattern pattern = new Pattern(rule, positions);
		pattern.setDescription(desc.toString());
		return pattern;
	}

	public static Pattern createFromFile(File file) throws FileNotFoundException {
		Pattern pattern = null;
		if (file.getName().toLowerCase().endsWith(".lif")) {
			pattern = loadLIF(new FileInputStream(file));
		}
		else if (file.getName().toLowerCase().endsWith(".rle")) {
			pattern = loadRLE(new FileInputStream(file));
		}
		else {
			throw new IllegalArgumentException("Unrecognized file extension.");
		}
		pattern.setName(file.getName());
		return pattern;
	}

	public void saveToFile(String filePath) {
		if (!filePath.endsWith(".rle")) {
			filePath += ".rle";
		}
		File file = new File(filePath);
		setName(file.getName().substring(0, file.getName().length() - 4));
		try {
			FixedLineLengthWriter writer = new FixedLineLengthWriter(new FileWriter(filePath), 70);
			writer.writeLine("#C " + getName());

			int startX = positions.first().x;
			int startY = positions.first().y;
			for (Position position : positions) {
				if (position.x < startX) {
					startX = position.x;
				}
				if (position.y < startY) {
					startY = position.y;
				}
			}
			writer.writeLine("x = " + startX + ", y = " + startY + ", rule = " + getRule());

			int x = startX;
			int y = startY;
			int runCount = 0;
			for (Position position : positions) {
				int delta = position.y - y;
				if (delta > 0) {
					if (runCount > 0) {
						writer.append((runCount == 1 ? "" : runCount) + "o");
						runCount = 0;
					}
					writer.append((delta == 1 ? "" : delta) + "$");
					y = position.y;
					x = startX;
				}
				delta = position.x - x;
				if (delta > 0) {
					if (runCount > 0) {
						writer.append((runCount == 1 ? "" : runCount) + "o");
						runCount = 0;
					}
					writer.append((delta == 1 ? "" : delta) + "b");
				}
				runCount++;
				x = position.x + 1;
			}
			if (runCount > 0) {
				writer.append((runCount == 1 ? "" : runCount) + "o");
			}
			writer.writeLine("!");
			writer.close();
		} catch (IOException e) {
			System.err.println(e);
		}
	}

	protected static class FixedLineLengthWriter {

		private BufferedWriter writer;
		private StringBuilder builder = new StringBuilder();
		private int lineLength;

		public FixedLineLengthWriter(Writer writer, int lineLength) {
			this.writer = new BufferedWriter(writer);
			this.lineLength = lineLength;
		}

		public void writeLine(String s) throws IOException {
			flush();
			writer.write(s);
			writer.newLine();
		}

		public void append(String s) throws IOException {
			if (builder.length() + s.length() > lineLength) {
				writer.write(builder.toString());
				writer.newLine();
				builder.delete(0, builder.length());
			}
			builder.append(s);
		}

		public void flush() throws IOException {
			if (builder.length() > 0) {
				writer.write(builder.toString());
				builder.delete(0, builder.length());
			}
		}

		public void close() throws IOException {
			flush();
			writer.close();
		}
	}
}
